import requests
from bs4 import BeautifulSoup
import threading
import time
import random


class WebCrawler:
    def __init__(self, headers=None, timeout=10, thread_num=5):
        """初始化爬虫：设置请求头、超时时间、线程数"""
        self.headers = headers or {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36"
        }
        self.timeout = timeout
        self.thread_num = thread_num
        self.crawled_urls = set()  # 记录已爬取的URL，避免重复
        self.lock = threading.Lock()  # 线程锁，保证共享数据安全

    def requestpage(self, url):
        """发送请求并返回响应，处理异常"""
        try:
            time.sleep(random.uniform(1, 3))  # 随机延迟，规避反爬
            print(f"[调试] 正在请求: {url}")
            response = requests.get(url, headers=self.headers, timeout=self.timeout)
            print(f"[调试] 响应状态码: {response.status_code}")
            print(f"[调试] 响应内容长度: {len(response.text)} 字符")
            response.raise_for_status()  # 若状态码非200，抛异常
            return response
        except Exception as e:
            print(f"[错误] 请求失败：{url}，错误原因：{str(e)}")
            return None

    def parsepage(self, response, url):
        """解析网页，提取标题、正文、标签（需根据目标网站调整选择器）"""
        if not response:
            print(f"[调试] 响应为空，无法解析: {url}")
            return None
        
        soup = BeautifulSoup(response.text, "html.parser")
        print(f"[调试] 开始解析页面: {url}")
        
        # 策略1: 尝试博客类网站的选择器（原始方法）
        title = soup.find("h1", class_="post-title")
        content = soup.find("div", class_="post-content")
        tags = soup.find_all("a", class_="tag")
        
        # 策略2: 如果策略1失败，尝试更通用的选择器
        if not title:
            title = soup.find("h1") or soup.find("title")
            print(f"[调试] 使用通用选择器查找标题: {title is not None}")
        
        if not content:
            # 尝试常见的正文容器
            content = (soup.find("div", class_="content") or 
                      soup.find("article") or 
                      soup.find("main") or
                      soup.find("div", id="content") or
                      soup.find("div", class_="post-body"))
            print(f"[调试] 使用通用选择器查找正文: {content is not None}")
        
        if not tags:
            # 尝试常见的标签选择器
            tags = (soup.find_all("a", class_="tag") or 
                   soup.find_all("span", class_="tag") or
                   soup.find_all("meta", property="article:tag"))
            print(f"[调试] 找到标签数量: {len(tags)}")
        
        # 如果还是没有找到标题和正文，尝试提取所有文本
        if not title or not content:
            print(f"[警告] 无法找到标题或正文，尝试提取页面所有文本")
            # 提取body中的所有文本
            body = soup.find("body")
            if body:
                all_text = body.get_text(separator="\n", strip=True)
                # 取前1000字符作为内容
                if all_text:
                    title_text = soup.find("title").get_text().strip() if soup.find("title") else "无标题"
                    content_text = all_text[:1000] if len(all_text) > 1000 else all_text
                    tags_text = []
                    
                    print(f"[调试] 使用备用方案提取内容，长度: {len(content_text)}")
                    return {
                        "url": url,
                        "title": title_text,
                        "content": content_text,
                        "tags": tags_text,
                        "crawl_time": time.strftime("%Y-%m-%d %H:%M:%S"),
                        "parse_method": "fallback"
                    }
            else:
                print(f"[错误] 无法解析页面，可能原因：1) 页面是SPA应用（需要JavaScript渲染） 2) 反爬机制拦截 3) 页面结构不匹配")
                return None

        # 提取文本
        title_text = title.get_text().strip() if title else "无标题"
        content_text = content.get_text().strip() if content else ""
        tags_text = [tag.get_text().strip() for tag in tags] if tags else []

        print(f"[调试] 解析成功 - 标题: {title_text[:50]}..., 内容长度: {len(content_text)}")
        
        return {
            "url": url,
            "title": title_text,
            "content": content_text,
            "tags": tags_text,
            "crawl_time": time.strftime("%Y-%m-%d %H:%M:%S"),
            "parse_method": "standard"
        }

    def crawl_single_url(self, url, result_list):
        """单线程爬取一个URL，结果存入result_list"""
        with self.lock:
            if url in self.crawled_urls:
                return
            self.crawled_urls.add(url)
        response = self.requestpage(url)
        data = self.parsepage(response, url)
        if data:
            result_list.append(data)

    def crawl_multi_urls(self, urls):
        """多线程爬取多个URL"""
        result_list = []
        threads = []
        for i, url in enumerate(urls):
            thread = threading.Thread(
                target=self.crawl_single_url,
                args=(url, result_list)
            )
            threads.append(thread)
            thread.start()
            # 控制线程数，避免过多线程压垮目标站
            if (i + 1) % self.thread_num == 0 or i == len(urls) - 1:
                for t in threads:
                    t.join()
                threads = []
        return result_list


if __name__ == "__main__":
    # 测试：爬取几个示例URL
    # 注意：小红书(www.xiaohongshu.com)是SPA应用，需要JavaScript渲染，直接用requests无法获取内容
    # 建议使用支持JavaScript渲染的工具（如Selenium、Playwright）或使用其他可爬取的网站进行测试
    target_urls = [
        # 示例1：CSDN博客（更容易爬取，用于测试）
        "https://blog.csdn.net/qianguaqiangua/article/details/146372808",
        # 示例2：小红书（SPA应用，可能无法直接获取内容）
        # "https://www.xiaohongshu.com/explore",
        # 示例3：其他可爬取的网站
        # "https://www.example.com"
    ]
    
    print("=" * 60)
    print("开始爬取...")
    print("=" * 60)
    
    crawler = WebCrawler(thread_num=3)
    crawled_data = crawler.crawl_multi_urls(target_urls)

    # 打印结果示例
    print(f"\n{'='*60}")
    print(f"爬取完成！共获得 {len(crawled_data)} 条数据")
    print(f"{'='*60}\n")
    
    if not crawled_data:
        print("[警告] 没有获取到任何数据！")
        print("可能的原因：")
        print("1. 目标网站是SPA应用（需要JavaScript渲染），建议使用Selenium或Playwright")
        print("2. 目标网站有反爬机制，需要更复杂的处理（如登录、验证码等）")
        print("3. CSS选择器不匹配，需要根据实际网站结构调整")
        print("4. 网络连接问题或请求被拒绝")
    else:
        for idx, data in enumerate(crawled_data, 1):
            print(f"\n【数据 {idx}】")
            print(f"URL: {data['url']}")
            print(f"标题：{data['title']}")
            print(f"内容：{data['content'][:200]}..." if len(data['content']) > 200 else f"内容：{data['content']}")
            print(f"标签：{', '.join(data['tags']) if data['tags'] else '无标签'}")
            print(f"解析方法：{data.get('parse_method', 'unknown')}")
            print(f"爬取时间：{data['crawl_time']}")
            print("-" * 50)